<?php
declare(strict_types=1);

namespace app\shop\service;

use app\shop\enums\delivery\DeliveryMode;
use app\shop\enums\delivery\FreightType;
use app\shop\enums\delivery\MiniAppDeliveryMethod;
use app\shop\enums\goods\GoodsDeliveryType;
use app\shop\enums\order\OrderDeliveryType;
use app\shop\exception\FailException;
use app\shop\library\calculate\Calculate;
use app\shop\library\delivery\DeliveryProvider;
use app\shop\library\ShopHelper;
use app\shop\library\ShopLogHelper;
use app\shop\model\delivery\DeliveryCompanyModel;
use app\shop\model\delivery\DeliveryTemplateDetailModel;
use app\shop\model\order\OrderModel;
use app\shop\model\user\UserAddressModel;
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException;
use EasyWeChat\MiniApp\Application;
use Exception;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
use Throwable;

use function date;

class DeliveryService
{
    /**
     * 获取商品列表的运费模板
     * @param array $goodsList
     * @param array $userAddress
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public static function getGoodsListWithTemplate(array $goodsList, array $userAddress): array
    {
        $templateIds = array_map(function ($item) {
            return $item['goods']['delivery_template_id'];
        }, $goodsList);
        $templateIds = array_unique($templateIds);

        $templates = DeliveryTemplateDetailModel::whereIn('template_id', $templateIds)
            ->whereFindInSet('city_id', $userAddress['city'])
            ->field(['template_id', 'first', 'first_price', 'continue', 'continue_price'])
            ->select()
            ->toArray();

        foreach ($goodsList as &$goods) {
            foreach ($templates as $template) {
                if ($goods['goods']['delivery_template_id'] == $template['template_id']) {
                    $goods['delivery_template'] = $template;
                    break;
                }
            }
        }

        return $goodsList;
    }

    /**
     * 计算运费
     * @param array  $goodsList 购买商品
     * @param string $addressId 地址ID
     * @return string
     * @throws DataNotFoundException
     * @throws DbException
     * @throws FailException
     * @throws ModelNotFoundException
     */
    public static function calculateFreight(array $goodsList, string $addressId): string
    {
        $address = UserAddressModel::where(['id' => $addressId])->findOrEmpty();
        if ($address->isEmpty()) {
            return '0';
        }
        $userAddress = $address->toArray();

        $goodsList    = self::getGoodsListWithTemplate($goodsList, $userAddress);
        $totalFreight = '0';
        foreach ($goodsList as $goods) {
            if ($goods['goods']['delivery_type'] == GoodsDeliveryType::TEMPLATE && empty($goods['delivery_template'])) {
                throw new FailException("很抱歉，商品【{$goods['goods']['name']}】暂不支持配送到该地区");
            }
            $totalFreight = Calculate::newDecimal($totalFreight)->add(self::calculateSingleFreight($goods))->getValue();
        }

        return $totalFreight;
    }

    /**
     * 计算单个商品运费
     * @param array $goods
     * @return string
     */
    private static function calculateSingleFreight(array $goods): string
    {
        if ($goods['goods']['delivery_type'] == GoodsDeliveryType::FIXED) {
            return $goods['goods']['freight'];
        }

        $template = $goods['delivery_template'];

        // 运费模板类型 1:按件数 2:按重量
        $delivery_field =
            [
                FreightType::NUM    => 'num',
                FreightType::WEIGHT => 'total_weight',
                FreightType::VOLUME => 'total_volume',
            ][$goods['template']['type']] ?? FreightType::NUM;

        // 首件
        $freight = $template['first_price'];
        // 续件
        if ($goods[$delivery_field] > $template['first'] && $template['continue'] > 0) {
            $remain          = $goods[$delivery_field] - $template['first'];
            $continueFreight = Calculate::newDecimal($remain)->mul($template['continue_price'])->getValue();
            $freight         = Calculate::newDecimal($freight)->add($continueFreight)->getValue();
        }

        return $freight;
    }

    /**
     * 获取物流信息
     * @param string $deliveryNo        物流单号
     * @param int    $deliveryCompanyId 物流公司ID
     * @param string $phone             收件人手机号
     * @param string $openid
     * @return array 购买人openid
     * @throws Exception
     */
    public function getDeliveryTracks(string $deliveryNo, int $deliveryCompanyId, string $phone, string $openid): array
    {
        $config         = ShopHelper::get_setting_group('delivery_group', true, true);
        $deliveryConfig = $config['delivery_config'] ?? [];
        if (empty($deliveryConfig)) {
            throw new Exception('物流配置异常');
        }
        $deliveryCompanyCode = $this->getDeliveryCompanyCode($deliveryCompanyId);

        $queryOptions = [
            'delivery_no'           => $deliveryNo,
            'delivery_company_code' => $deliveryCompanyCode,
            'receiver_phone'        => $phone,
            'openid'                => $openid,
        ];

        return (new DeliveryProvider($deliveryConfig['delivery_api_provider']))->query($queryOptions);
    }

    /**
     * 获取物流公司编码
     * @param int $deliveryCompanyId
     * @return string
     */
    public function getDeliveryCompanyCode(int $deliveryCompanyId): string
    {
        $deliveryCompany = DeliveryCompanyModel::where(['id' => $deliveryCompanyId])->findOrEmpty();

        if ($deliveryCompany->isEmpty()) {
            return '';
        }

        $codeField = ShopHelper::get_delivery_company_code_field();

        return $deliveryCompany[$codeField] ?? '';
    }

    /**
     * 获取小程序对象
     * @return Application
     * @throws FailException
     */
    public function getMiniappObj(): Application
    {
        $miniappConfig = ShopHelper::get_setting_group('miniapp_group');

        try {
            return new Application([
                'app_id' => $miniappConfig['miniapp_id'],
                'secret' => $miniappConfig['miniapp_secret'],
            ]);
        } catch (InvalidArgumentException $e) {
            throw new FailException('小程序配置信息异常', null, $e);
        }
    }

    /**
     * 检查小程序是否已开通发货信息管理服务
     * @return bool
     * @throws FailException
     */
    public function checkSyncToMiniappPermissions(): bool
    {
        try {
            $app = $this->getMiniappObj();

            $response = $app->getClient()->post('/wxa/sec/order/is_trade_managed', [
                'json' => [
                    'appid' => $app->getAccount()->getAppId(),
                ]
            ]);
            if ($response->isFailed() || $response['errcode'] != 0) {
                ShopLogHelper::error('查询小程序是否已开通发货信息管理服务异常', [
                    'response' => $response->toArray(),
                ]);
                return false;
            }
        } catch (Throwable $e) {
            ShopLogHelper::error('查询小程序是否已开通发货信息管理服务异常', [], $e);
            throw new FailException($e->getMessage());
        }

        return $response['is_trade_managed'] == true;
    }

    /**
     * 构造虚拟发货同步至微信小程序的发货管理参数
     * @param string $tradeNo
     * @param string $openid
     * @param string $desc
     * @return array
     */
    public function buildVirtualDeliveryOptionsSyncToMiniapp(string $tradeNo, string $openid, string $desc): array
    {
        return $this->buildSyncDeliveryToMiniappOptions($tradeNo, MiniAppDeliveryMethod::VIRTUAL, DeliveryMode::UNIFIED_DELIVERY, $openid, [
            ['item_desc' => $desc,]
        ]);
    }

    /**
     * 构造同步至微信小程序发货管理参数
     * @param string $tradeNo        交易单号
     * @param int    $deliveryMethod 发货方式：1、快递物流 2、同城配送 3、虚拟商品 4、用户自提
     * @param int    $deliveryMode   物流模式：1=统一发货, 2=分拆发货
     * @param string $openid         用户openid
     * @param array  $shippingList
     *                               <br>item_desc 商品描述(必须)
     *                               <br>express_company 物流公司编码(发货类型为物流快递时必须)
     *                               <br>tracking_no 物流单号(发货类型为物流快递时必须)
     *                               <br>contact => [ 'receiver_contact' => '' ] 收货人电话(发货类型为物流快递时必须)
     * @return array
     */
    public function buildSyncDeliveryToMiniappOptions(string $tradeNo, int $deliveryMethod, int $deliveryMode, string $openid, array $shippingList): array
    {
        $options                  = [
            'order_key'        => [
                // 订单单号类型：1使用下单商户号和商户侧单号 2使用微信支付单号
                'order_number_type' => 2,
                // 支付交易对应的微信订单号
                'transaction_id'    => $tradeNo,
            ],
            // 物流模式，发货方式枚举值：1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品，虚拟商品，例如话费充值，点卡等，无实体配送形式 4、用户自提
            'logistics_type'   => $deliveryMethod,
            // 发货模式，发货模式枚举值：1、（统一发货）2、（分拆发货）
            'delivery_mode'    => $deliveryMode,
            // 是否已全部发货完成 分拆发货模式时必填，用于标识分拆发货模式下是否已全部发货完成，只有全部发货完成的情况下才会向用户推送发货完成通知。
            'is_all_delivered' => true,
            // 上传时间 (RFC3339格式)
            'upload_time'      => date(DATE_RFC3339),
            // 微信用户openid
            'payer'            => [
                'openid' => $openid
            ],
        ];
        $options['shipping_list'] = $shippingList;

        return $options;
    }

    /**
     * 同步发货信息到小程序
     * @param int $orderId
     * @return bool
     * @throws FailException
     */
    public function syncDeliveryInfoToMiniApp(int $orderId): bool
    {
        $order = OrderModel::where(['id' => $orderId])->with([
            'detail', 'user', 'payment_trade', 'address',
            'delivery' => function ($query) {
                $query->with(['delivery_company'])->limit(10);
            }
        ])->findOrEmpty()->visible([
            'detail'        => ['goods_name'],
            'user'          => ['openid'],
            'payment_trade' => ['trade_no'],
            'address'       => ['phone'],
            'delivery'      => ['delivery_no', 'type', 'delivery_company']
        ]);

        if ($order->isEmpty()) {
            throw new FailException("订单不存在");
        }
        if (!ShopHelper::is_third_party_payment($order['pay_way'])) {
            return true;
        }
        $receiverPhone = substr_replace($order['address']['phone'], '****', 3, 4);

        $tradeNo      = $order['payment_trade']['trade_no'];
        $deliveryMode = $order['delivery_mode'];
        $openid       = $order['user']['openid'];

        $desc = count($order['detail']) > 1 ? $order['detail'][0]['goods_name'] . "等多件商品" : $order['detail'][0]['goods_name'] . '*' . $order['total_num'];
        if (strlen($desc) > 120) {
            $desc = mb_substr($desc, 0, 117) . '...';
        }

        //快递服务商
        $companyCodeField = ShopHelper::get_delivery_company_code_field();

        $delivery = $order->toArray()['delivery'];
        if ($deliveryMode == DeliveryMode::SPLIT_DELIVERY) {    //分拆发货
            $deliveryType = MiniAppDeliveryMethod::EXPRESS;

            //过滤没有实际物流的发货
            $delivery = array_filter($delivery, function ($item) {
                return !empty($item['delivery_no']);
            });

            if (empty($delivery)) {
                // 转为统一发货，方式为虚拟发货
                $shippingList = [
                    [
                        'item_desc'       => $desc,
                        'contact'         => [
                            'receiver_contact' => $receiverPhone,
                        ],
                        'tracking_no'     => '',
                        'express_company' => ''
                    ]
                ];

                $options = $this->buildSyncDeliveryToMiniappOptions($tradeNo, MiniAppDeliveryMethod::VIRTUAL, DeliveryMode::UNIFIED_DELIVERY, $openid, $shippingList);
            } else {
                $shippingList = array_map(function ($item) use ($desc, $receiverPhone, $companyCodeField) {
                    return [
                        'item_desc'       => $desc,
                        'contact'         => [
                            'receiver_contact' => $receiverPhone,
                        ],
                        'tracking_no'     => $item['delivery_no'],
                        'express_company' => $item['delivery_company'][$companyCodeField]
                    ];
                }, $delivery);

                $options = $this->buildSyncDeliveryToMiniappOptions($tradeNo, $deliveryType, $deliveryMode, $openid, $shippingList);
            }
        } else {    //统一发货
            $delivery     = $delivery[0];
            $deliveryType = match (intval($delivery['type'])) {
                OrderDeliveryType::EXPRESS => MiniAppDeliveryMethod::EXPRESS,   //快递发货
                OrderDeliveryType::WITHOUT_EXPRESS => MiniAppDeliveryMethod::VIRTUAL,   //无需物流 虚拟发货
            };
            $shippingList = [
                [
                    'item_desc'       => $desc,
                    'contact'         => [
                        'receiver_contact' => $receiverPhone,
                    ],
                    'tracking_no'     => $delivery['delivery_no'] ?? '',
                    'express_company' => $delivery['delivery_company'][$companyCodeField] ?? ''
                ]
            ];

            $options = $this->buildSyncDeliveryToMiniappOptions($tradeNo, $deliveryType, $deliveryMode, $openid, $shippingList);
        }

        $this->setReceiveJumpPath('/pagesOrder/pages/center/center');

        return $this->requestSyncDeliveryInfo($options);
    }

    /**
     * 同步微信发货信息请求
     * @param array $options
     * @return bool
     * @throws FailException
     */
    public function requestSyncDeliveryInfo(array $options): bool
    {
        // 校验服务是否已经开通
        if (!$this->checkSyncToMiniappPermissions()) {
            return false;
        }

        try {
            $app = $this->getMiniappObj();

            $response = $app->getClient()->postJson('/wxa/sec/order/upload_shipping_info', $options);
            if ($response->isFailed() || $response['errcode'] != 0) {
                ShopLogHelper::error('小程序物流信息同步错误', [
                    'response' => $response->toArray(),
                    'options'  => $options,
                ]);
                return false;
            }
        } catch (Throwable $e) {
            ShopLogHelper::error('小程序物流信息同步异常', $options, $e);
            throw new FailException($e->getMessage());
        }

        return true;
    }

    /**
     * 查询微信订单状态
     * @param string $tradeNo
     * @return false|mixed
     * @throws FailException
     */
    public function getOrderFromWechat(string $tradeNo): mixed
    {
        $options = [
            'transaction_id' => $tradeNo
        ];

        try {
            $app      = $this->getMiniappObj();
            $response = $app->getClient()->postJson('/wxa/sec/order/get_order', $options);
            if ($response->isFailed() || $response['errcode'] != 0) {
                ShopLogHelper::error('查询订单发货状态错误', [
                    'response' => $response->toArray(),
                    'options'  => $options,
                ]);
                return false;
            }
        } catch (Throwable $e) {
            ShopLogHelper::error('查询订单发货状态异常', $options, $e);
            throw new FailException($e->getMessage());
        }

        return $response['order'];
    }

    /**
     * 检查订单是否已结算
     * @param string $tradeNo
     * @return bool
     * @throws FailException
     */
    public function checkOrderSettlement(string $tradeNo): bool
    {
        $order = $this->getOrderFromWechat($tradeNo);
        ShopLogHelper::info('查询订单结算状态', ['order' => $order]);
        // 订单状态枚举：(1) 待发货；(2) 已发货；(3) 确认收货；(4) 交易完成；(5) 已退款。
        return in_array($order['order_state'], [3, 4]);
    }

    /**
     * 提醒用户确认收货
     * @param string $tradeNo
     * @return bool
     */
    public function notifyConfirmReceive(string $tradeNo): bool
    {
        try {
            $app = $this->getMiniappObj();

            $response = $app->getClient()->post('/wxa/sec/order/notify_confirm_receive', [
                'json' => [
                    'transaction_id' => $tradeNo,
                    'received_time'  => time(),
                ]
            ]);
            $errorMsg = $response->isFailed() ? '请求失败' : '微信返回错误';
            $logData  = [
                'response' => $response->toArray(),
            ];
            if ($response->isFailed() || $response['errcode'] != 0) {
                ShopLogHelper::error($errorMsg, $logData);
                return false;
            }
        } catch (Throwable $e) {
            ShopLogHelper::error('确认收货提醒异常', ['trade_no' => $tradeNo], $e);
            return false;
        }

        return true;
    }

    /**
     * 设置用户点击微信发货通知时跳转页面路径
     * @param string $path
     * @return bool
     */
    public function setReceiveJumpPath(string $path): bool
    {
        try {
            $app = $this->getMiniappObj();

            $response = $app->getClient()->post('/wxa/sec/order/set_msg_jump_path', [
                'json' => [
                    // 小程序订单详情页路径
                    'path' => $path
                ]
            ]);
            $errorMsg = $response->isFailed() ? '请求失败' : '微信返回错误';
            $logData  = [
                'response' => $response->toArray(),
            ];
            if ($response->isFailed() || $response['errcode'] != 0) {
                ShopLogHelper::error($errorMsg, $logData);
                return false;
            }
        } catch (Throwable $e) {
            ShopLogHelper::error('设置用户点击微信发货通知时跳转页面路径异常', ['path' => $path], $e);
            return false;
        }

        return true;
    }
}